﻿#region Header
//
//	Project:		AllegroNET
//	Description:	Silverlight port of Allegro (http://alleg.sourceforge.net/)
//
//	Perfect pixel collision detection
//	Silverlight port of PPCOL library for Allegro (http://ibaldo.codigolibre.net/ppcol.php)
//
//	Copyright © 2010 Andrey Gayvoronsky
//
//	Please read the License.txt for details.
//
#endregion

using System;

namespace AllegroNET
{
	public class PPCOL_MASK : IDisposable
	{
		private bool isDisposed = false;

		public int h;			//Bounding box height.
		public int w;			//Bounding box width.
		public int max_chunk;	//This determines the total number of 32bits chunks - 1.
		public uint[] dat;		//The mask data pointer.
		public int[] sp_mask;	//Pointers to the begining of each row of chunks.

		~PPCOL_MASK()
		{
			Dispose(false);
		}

		#region IDisposable Members
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing)
		{
			if (!isDisposed)
			{
				if (disposing) //actually Dispose is not very needed right now, but better reserve it for future and also for destroy_* functions
				{
					dat = null;
					sp_mask = null;
				}
				
				isDisposed = true;
			}
		}
		#endregion
	};

	public partial class Allegro
	{
		/* create_ppcol_mask:
		 * This is the mask creation routine. Given a sprite it creates a mask for that sprite. 
		 * Returns a pointer to a PPCOL_MASK or null if there is a problem.
		 */ 
		public static PPCOL_MASK create_ppcol_mask(BITMAP sprite)
		{
			if (sprite == null)
				return null;

			PPCOL_MASK maskout;
			int y, chunk, x;

			maskout = new PPCOL_MASK();
			maskout.sp_mask = new int[sprite.h];

			maskout.h = sprite.h;
			maskout.w = sprite.w;

			//not divisible by 32? yes: no;
			maskout.max_chunk = ((sprite.w % 32 != 0) ? (sprite.w / 32) : (sprite.w / 32 - 1));
			maskout.dat = new uint[(maskout.max_chunk + 1) * sprite.h];

			//We are going to initialize the array of pointers to the begining of each row of chunks...
			maskout.sp_mask[0] = 0; //The first pointer points to the begining of the data...

			for (y = 1; y < maskout.h; y++) //Now initialize all the other pointers...
				maskout.sp_mask[y] = maskout.sp_mask[y - 1] + (maskout.max_chunk + 1); //the position of the previous pointer + number of the chunks.

			//Now we have a proper mask structure allocated and mostly initialized, 
			//Buffer.SetByte(maskout.dat, 0, 0); //Looks, like C# already cleaned up

			//Now we have to create the bit mask array for the sprite:
			for (y = 0; y < maskout.h; y++)
			{ //Row loop...
				for (chunk = 0; chunk <= maskout.max_chunk; chunk++)
				{ //Chunk loop...
					for (x = chunk * 32; x < (chunk * 32 + 32); x++)
					{ //Loop between the pixels that the current chunk covers...
						if (x < sprite.w)
						{
							//Check if we are in range in the X axis... because if we pass it, 
							//getpixel would return a bad return value...
							if ((getpixel(sprite, x, y) & 0x00FFFFFF) != MASK_COLOR_32) //remove alpha-channel value
							{
								//The pixel is not a mask color... so we have to check for collision, 
								//so we add it to the bitarray in the current chunk...
								maskout.dat[maskout.sp_mask[y] + chunk] += 0x80000000 >> (x - (chunk) * 32); //This seems more correct... //0x80000000 == 0b10000000000000000000000000000000, we shift this bit to the right position where it must be in the current chunk, this indicates that there is a pixel that we must check for collision.
							}
						}
					}
				}
			}

			return maskout;
		}
		
		/* destroy_ppcol_mask:
		 * Destroys mask object.
		 */
		static public void destroy_ppcol_mask(PPCOL_MASK mask)
		{
			mask.Dispose();
		}

		/* check_bb_collision:
		 * This is the bounding box collision detection.
		 * Pass it the coordinates of one rectangle and the width and
		 * height of it, then pass the coordinates of a second rectangle and the width and height of
		 * it. It will return 0 if theres not collision or 1 if there is a collision between the
		 * rectangles (the rectangles overlap).
		 * This function works looking for out of range values, if some value is out of
		 * range it returns 0, if all the values are in range it returns true.
		*/
		static public bool check_bb_collision(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2)
		{
			return (!(((x1) >= (x2) + (w2)) || ((x2) >= (x1) + (w1)) || ((y1) >= (y2) + (h2)) || ((y2) >= (y1) + (h1))));
		}

		static public bool check_bb_collision(PPCOL_MASK mask1, PPCOL_MASK mask2, int x1, int y1, int x2, int y2)
		{
			return check_bb_collision(x1, y1, mask1.w, mask1.h, x2, y2, mask2.w, mask2.h);
		}

		/* check_ppmask_collision:
		 * This is the pixel perfect collision detection routine. Pass it 2 properly
		 * initialized sprite masks and then their respective X, Y positions.
		 * Returns false if there is not collision and true if there is a collision.
		 */
		static public bool check_ppmask_collision(PPCOL_MASK mask1, PPCOL_MASK mask2, int x1, int y1, int x2, int y2)
		{
			//First we do normal bounding box collision detection...
			if (!check_bb_collision(x1, y1, mask1.w, mask1.h, x2, y2, mask2.w, mask2.h))
				return false;

			int dx1, dx2, dy1, dy2;
			int py;
			int maxh;

			int chunkm1, chunkm2;
			int chunkm1p, chunkm2p;

			//Now we do pixel perfect collision detection...

			//First we need to see how much we have to shift the coordinates of the masks...
			if (x1 > x2)
			{
				dx1 = 0;	 //don't need to shift mask 1.
				dx2 = x1 - x2;	//shift mask 2 left. Why left? Because we have the mask 1 being on the right of the mask 2, so we have to move mask 2 to the left to do the proper pixel perfect collision...
			}
			else
			{
				dx1 = x2 - x1;  //shift mask 1 left.
				dx2 = 0;      //don't need to shift mask 2.
			}
			if (y1 > y2)
			{
				dy1 = 0;
				dy2 = y1 - y2;  //we need to move this many rows up mask 2. Why up? Because we have mask 1 being down of mask 2, so we have to move mask 2 up to do the proper pixel perfect collision detection...
			}
			else
			{
				dy1 = y2 - y1;  //we need to move this many rows up mask 1.
				dy2 = 0;
			}

			chunkm1 = dx1 / 32; //This sets the starting chunk number...
			chunkm2 = dx2 / 32;
			chunkm1p = dx1 - (32 * chunkm1); //This sets the value we have to shift the chunks of this mask...
			chunkm2p = dx2 - (32 * chunkm2);

			//This will calculate the maximum height that we will reach...
			if (mask1.h - dy1 > mask2.h - dy2)
			{
				maxh = mask2.h - dy2;
			}
			else
			{
				maxh = mask1.h - dy1;
			}
			maxh--;

			//Now we start the actual pixel perfect collision detection loop...
			while ((chunkm1 <= mask1.max_chunk) && (chunkm2 <= mask2.max_chunk))
			{ //While there remains more chunks in the 2 masks we continue doing the ppcol detection...
				for (py = maxh; py >= 0; py--)
				{ //Test for all rows...
					if (((mask1.dat[mask1.sp_mask[dy1 + py] + chunkm1] << chunkm1p) & (mask2.dat[mask2.sp_mask[dy2 + py] + chunkm2] << chunkm2p)) != 0) //If bits of the mask coincides, we have a collision!
						return true; //Return that there is a collision!
				}
				//Now we have to move to the next chunk...
				if ((chunkm1p == 0) && (chunkm2p == 0))
				{ //In case both masks are lined up on the x axis...
					chunkm1++; //Increment both chunks...
					chunkm2++;
				}
				else
				{
					if (chunkm1p == 0)
					{ //If the mask 1 don't need shifting...
						chunkm2++;
						chunkm1p = 32 - chunkm2p;
						chunkm2p = 0;
					}
					else
					{
						if (chunkm2p == 0)
						{
							chunkm1++;
							chunkm2p = 32 - chunkm1p;
							chunkm1p = 0;
						}
					}
				}

			}

			return false;
		}

		/* draw_ppmask:
		 * Draw mask object with specified color.
		 */
		static public void draw_ppmask(BITMAP bmp, PPCOL_MASK mask, int x, int y, int color)
		{
			int row, chunk, pos;

			for (row = 0; row < mask.h; row++)
				for (chunk = 0; chunk <= mask.max_chunk; chunk++)
					for (pos = 0; (pos < 32) && ((pos + (chunk * 32)) < mask.w); pos++)
						if ((mask.dat[mask.sp_mask[row] + chunk] & (0x80000000 >> pos)) != 0)
							putpixel(bmp, x + pos + (chunk * 32), y + row, color);
		}
	}
}
